/*
    This file is part of FSJController.

    FSJController is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    FSJController is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with FSJController.  If not, see <http://www.gnu.org/licenses/>.
*/

package de.qsc.fsjcontroller;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Encapsulates a Freeswitch event
 * 
 * @author Roland Hänel <rha@qsc.de>
 */
public class FSEvent {

    private static final Logger logger = LoggerFactory.getLogger(FSEvent.class);
    
    public enum Name {
        ADD_SCHEDULE,
        ALL,
        API,
        BACKGROUND_JOB,
        CALL_SECURE,
        CALL_UPDATE,
        CHANNEL_ANSWER,
        CHANNEL_APPLICATION,
        CHANNEL_BRIDGE,
        CHANNEL_CALLSTATE,
        CHANNEL_CREATE,
        CHANNEL_DATA,
        CHANNEL_DESTROY,
        CHANNEL_EXECUTE,
        CHANNEL_EXECUTE_COMPLETE,
        CHANNEL_HANGUP,
        CHANNEL_HANGUP_COMPLETE,
        CHANNEL_HOLD,
        CHANNEL_ORIGINATE,
        CHANNEL_OUTGOING,
        CHANNEL_PARK,
        CHANNEL_PROGRESS,
        CHANNEL_PROGRESS_MEDIA,
        CHANNEL_STATE,
        CHANNEL_UNBRIDGE,
        CHANNEL_UNHOLD,
        CHANNEL_UNPARK,
        CHANNEL_UUID,
        CLIENT_DISCONNECTED,
        CODEC,
        COMMAND,
        CUSTOM,
        CUSTOM_CONFERENCE_MAINTENANCE,
        CUSTOM_DINGALING_CONNECTED,
        CUSTOM_DINGALING_LOGIN_FAILURE,
        CUSTOM_DINGALING_LOGIN_SUCCESS,
        CUSTOM_PORTAUDIO_MAKECALL,
        CUSTOM_PORTAUDIO_RINGING,
        CUSTOM_SKINNY_ALARM,
        CUSTOM_SKINNY_CALL_STATE,
        CUSTOM_SKINNY_EXPIRE,
        CUSTOM_SKINNY_REGISTER,
        CUSTOM_SKINNY_UNREGISTER,
        CUSTOM_SOFIA_EXPIRE,
        CUSTOM_SOFIA_GATEWAY_STATE,
        CUSTOM_SOFIA_NOTIFY_REFERER,
        CUSTOM_SOFIA_REGISTER,
        CUSTOM_SOFIA_REINVITE,
        CUSTOM_SOFIA_UNREGISTER,
        CUSTOM_SPANDSP_RXFAXRESULT,
        CUSTOM_SPANDSP_TXFAXRESULT,
        CUSTOM_SPANDSP_RXFAXNEGOCIATERESULT,
        CUSTOM_SPANDSP_TXFAXNEGOCIATERESULT,
        CUSTOM_SPANDSP_RXFAXPAGERESULT,
        CUSTOM_SPANDSP_TXFAXPAGERESULT,
        DEL_SCHEDULE,
        DETECTED_SPEECH,
        DETECTED_TONE,
        DTMF,
        EXE_SCHEDULE,
        GENERAL,
        HEARTBEAT,
        INBOUND_CHAN,
        LOG,
        MEDIA_BUG_START,
        MEDIA_BUG_STOP,
        MESSAGE,
        MESSAGE_QUERY,
        MESSAGE_WAITING,
        MODULE_LOAD,
        MODULE_UNLOAD,
        NAT,
        NOTALK,
        NOTIFY,
        OUTBOUND_CHAN,
        PLAYBACK_START,
        PLAYBACK_STOP,
        PRESENCE_IN,
        PRESENCE_OUT,
        PRESENCE_PROBE,
        PRIVATE_COMMAND,
        PUBLISH,
        RECORD_START,
        RECORD_STOP,
        RECV_INFO,
        RECV_MESSAGE,
        RELOADXML,
        REQUEST_PARAMS,
        RE_SCHEDULE,
        ROSTER,
        SEND_INFO,
        SEND_MESSAGE,
        SERVER_DISCONNECTED,
        SESSION_CRASH,
        SESSION_HEARTBEAT,
        SHUTDOWN,
        SOCKET_DATA,
        STARTUP,
        TALK,
        TRAP,
        UNKNOWN,
        UNPUBLISH
    }

    Name name;
    UUID channel;
    HashMap<String, String> attributes;
    String content; 

    static final Pattern keyValuePattern = Pattern.compile("^\\s*(\\S+)\\s*:\\s*([\\S ]+)\\s*$", Pattern.MULTILINE);

    /**
     * Constructs a FSEvent object from textual representation
     * 
     * @param plainText
     *            - A plain text representation of the event (LF separated key: value)
     */
    protected FSEvent(String plainText) {
        attributes = new HashMap<String, String>(50);

        // extract key:value pairs
        Matcher m = keyValuePattern.matcher(plainText);
        while (m.find()) {
            try {
                attributes.put(m.group(1).toLowerCase(), URLDecoder.decode(m.group(2), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        
        // extract the event name
        String eventName = attributes.get("event-name");
        if (eventName != null) {
            // CUSTOM events require special treatment
            if (eventName.equals("CUSTOM")) {
                String eventSubclass = attributes.get("event-subclass");
                // construct the event name of CUSTOM and subclass
                if (eventSubclass != null)
                    eventName = "CUSTOM_" + eventSubclass.toUpperCase().replaceAll("::", "_");
            }
            // now the event should be one of our pre-defined enum values
            try {
                name = Name.valueOf(eventName);
            } catch (IllegalArgumentException e) {
                name = Name.UNKNOWN;
                logger.warn("Unknown freeswitch event {}", eventName);
            }
        } else {
            // no Event-Name, that shouldn't happen, but who knows...
            name = Name.UNKNOWN;
            logger.warn("Freeswitch event without event-name attribute");
        }
        
        String channelUUID = attributes.get("unique-id");
        if (channelUUID != null) {
            try {
                channel = UUID.fromString(channelUUID);
            } catch (IllegalArgumentException e) {
                channel = null;
                logger.warn("Cannot parse event UUID {} from event {}", channelUUID, name);
            }
        } else {
            channel = null;
        }
        
        // extract the content, if any
        String contentLength = getAttribute("Content-Length");
        if (contentLength != null) {
            try {
                int length = Integer.parseInt(contentLength);
                if (plainText.length() > length) {
                    content = plainText.substring(plainText.length() - length, plainText.length());
                } else {
                    content = null;
                }
            }
            catch (NumberFormatException e) {
                content = null;
            }
        } else {
            content = null;
        }
    }

    /**
     * @return the name of this event as an FSEvent.Name enum type
     */
    public Name getName() {
        return name;
    }
    
    /**
     * @return the channel UUID of this event
     */
    public UUID getChannelUUID() {
        return channel;
    }

    /**
     * Returns the specified attribute of this event
     * 
     * @param attribute
     *            - the attribute to return, e.g. "Unique-ID", case-insensitive
     * @return the value of the attribute if present, or null otherwise
     */
    public String getAttribute(String attribute) {
        return attributes.get(attribute.toLowerCase());
    }

    /**
     * Returns a map of all attributes of this event
     * 
     * @return the map of attributes
     */
    public Map<String, String> getAttributeMap() {
        return attributes;
    }
    
    
    public String getContent() {
        return content;
    }

    /**
     * Returns a textual representation of the FSEvent, mainly for debugging purposes
     */
    public String toString() {
        if (channel != null) {
            return name.toString() + " (channel " + channel + ")";
        } else {
            return name.toString();
        }
    }
}
